
# Import
import ConfigParser
import time
import thread
from Utils import *
from BMaxPrecompiler import *

# FileThread
class FileThread(Thread):
    
    def __init__(self, app, file):
        Thread.__init__(self)
        self.app = app
        self.file = file
        
    def run(self):
        self.app.precompiler.precompile(self.file)

# App
class App:
    
    def __init__(self):
        self.title = "Blitzprog Precompiler"
        self.version = "0.8.2"
        self.precompiler = None
        self.realStdout = sys.stdout
        self.realStderr = sys.stderr
        self.isBlitzcc = 0
        self.isBmk = 0
        self.isDebugMode = 0
        self.dir = fixPath(os.path.dirname(sys.argv[0]))
        if not self.dir:
            self.dir = "."
        
    def loadConfig(self, file, verbose = False):
        self.config = ConfigParser.ConfigParser()
        
        # Default values
        self.config.add_section("General")
        self.config.set("General", "Precompiler", "1")
        self.config.set("General", "Threading", "0")
        self.config.set("General", "DeletePreFiles", "1")
        self.config.set("General", "StartCompiler", "1")
        self.config.set("General", "LogAppendMessages", "0")
        
        self.config.add_section("Components")
        self.config.set("Components", "Profiler", "1")
        self.config.set("Components", "CodeStats", "1")
        self.config.set("Components", "DebugFunctions", "0")
        
        self.config.add_section("Profiler")
        self.config.set("Profiler", "DebugProfiler", "0")
        self.config.set("Profiler", "TopX", "20")
        self.config.set("Profiler", "MinimumCalls", "100")
        self.config.set("Profiler", "CollIDE", "0")
        self.config.set("Profiler", "RealTimeData", "0")
        self.config.set("Profiler", "AverageTimeFloatPrecision", "2")
        
        self.config.add_section("HTML")
        self.config.set("HTML", "TimeCritical", "1")
        self.config.set("HTML", "Style", "Default")
        
        if os.path.isfile(file):
            if verbose:
                print "Loading " + file + "\n"
            self.config.read(file)
        else:
            print "Couldn't find " + file + "\n"
        
    def precompile(self, arg):
        
        # Precompile
        self.precompiler = BMaxPrecompiler(self, self.config)
        self.precompiler.setMainFile(arg)
        self.precompiler.scan(arg)
        
        # Debug
        if self.precompiler.debug:
            print ""
            for item in self.precompiler.importFiles.keys():
                print "Import[" + str(self.precompiler.importFiles[item]) + "] = " + item  
        
        # Start measuring
        self.startTime = time.clock()
        
        # Precompile every imported file
        if int(self.config.get("General", "Threading")):
            print "|-------------------------------------------------------|"
            print "| Threading is enabled:                                 |"
            print "| Output messages will not appear in the correct order! |"
            print "|-------------------------------------------------------|"
            
            threadPool = []
            
            # Info
            print " Creating " + str(len(self.precompiler.importFiles)) + " threads"
            
            # Create a thread for every file
            for item in self.precompiler.importFiles.keys():
                print "  Thread: " + item
                thread = FileThread(self, item)
                thread.start()
                threadPool.append(thread)
                
            # Wait until every thread has finished his task
            for thread in threadPool:
                thread.join()
        else:
            for item in self.precompiler.importFiles.keys():
                self.precompiler.precompile(item)
        
        # Precompile main file
        self.precompiler.precompile(arg, True)
        
        # Create import file
        self.precompiler.createProfilerFile(self.precompiler.currentWorkingDir + "profilerLog.pre" + self.precompiler.extension)
        
        # Print data
        print ""
        self.precompiler.writeData(sys.stdout)
        print ""
        
        # Show execution time
        print "Precompiler: " + str(int((time.clock() - self.startTime) * 1000)) + " ms\n"
        
        # Start compiler
        self.startCompiler()
        
    def startCompiler(self, verbose = True, defaultArgs = False):
        
        # Return if the compiler shouldn't be started
        if not int(self.config.get("General", "StartCompiler")):
            return
        
        # Execute main file
        if verbose:
            if self.precompiler.isBMAX:
                print "Starting BMax compiler"
            elif self.precompiler.isBB:
                print "Starting BB compiler"
        
        # OS independent
        if os.name == "nt":
            binaryExtension = ".exe"
        else:
            binaryExtension = ""
        
        # Create call parameters
        bmkBinary = ""
        self.absDir = fixPath(os.path.abspath(extractDir(sys.argv[0])))
        if self.precompiler.isBMAX:                     # BMax
            bmkBinary = self.dir + "/" + "bmk2" + binaryExtension
        elif self.precompiler.isBB:                     # BB
            bmkBinary = self.dir + "/" + "blitzcc2" + binaryExtension
            
            # Set blitzpath
            if not "blitzpath" in os.environ:
                os.environ["blitzpath"] = self.absDir.replace("/bin", "")
                print "Set blitzpath to: " + os.environ["blitzpath"]
        
        # Check file
        bmkNeedsMode = 0
        if not os.access(bmkBinary, bmkNeedsMode):
            print "Couldn't find " + bmkBinary
            return
        
        # Whitespace in path
        #if " " in bmkBinary:
        #    bmkBinary = '"' + bmkBinary + '"'
        bmkCall = list()
        bmkCall.append(fixPathWindows(bmkBinary))
        
        outputSpecified = False
        bmkShouldExecute = False
        for bmkArg in range(1, len(sys.argv)):
            bmkArgStr = sys.argv[bmkArg]
            
            if not defaultArgs:
                if not bmkArgStr.endswith(".pre" + self.precompiler.extension):
                    bmkArgStr = bmkArgStr.replace(self.precompiler.extension, ".pre" + self.precompiler.extension)
                
                if bmkArgStr == "-o":
                    outputSpecified = True
                elif bmkArgStr == "-x":
                    bmkShouldExecute = True
                
                if not outputSpecified and bmkArgStr.endswith(".pre.bmx"):
                    #bmkCall += " -o \"" + bmkArgStr.replace(".pre.bmx", "") + "\""
                    bmkCall.append("-o")
                    bmkCall.append(fixPathWindows(bmkArgStr.replace(".pre.bmx", "")))
                    outputSpecified = True
                
                if bmkArgStr.endswith(".pre.bb"):
                    goTo = extractDir(fixPath(bmkArgStr))
                    print "File: " + bmkArgStr
                    print "CurrentDir: " + os.getcwd()
                    print "GoTo: " + goTo
                    
                    os.chdir(goTo)
                    print "CurrentDir: " + os.getcwd()
                    # bmkCall += ' "' + os.path.basename(bmkArgStr) + '"'
                    bmkCall.append(os.path.basename(bmkArgStr))
                    continue
            
            #if " " in bmkArgStr:
            bmkCall.append(bmkArgStr)
            #else:
            #    bmkCall += " " + bmkArgStr
        
        #if os.name == "nt":
        #    bmkCall = fixPathWindows(bmkCall)
        
        if verbose:
            print "Binary: " + bmkBinary
        #print bmkBinary
        #print sys.argv[1:]
        
        if verbose:
            print "Call: "
            print bmkCall
            print "\n"
            
            sys.stdout.flush()
        try:
            #os.execv(bmkBinary, sys.argv[1:])
            #os.spawnv(os.P_NOWAITO, bmkBinary, sys.argv[1:])
            import subprocess
            compilerStart = time.clock()
            bmk = subprocess.Popen(bmkCall, bufsize=0, cwd=os.path.abspath(os.curdir), env=None, stdout=self.realStdout, stderr=self.realStderr, close_fds=False, shell=True, universal_newlines=True)
            #bmk = subprocess.Popen(bmkCall)
            bmk.wait()
            
            bmkTasks = "\nBB/BMax compile time"
            if bmkShouldExecute:
                bmkTasks += " + execution time"
            if verbose:
                print bmkTasks + ": " + str(int((time.clock() - compilerStart) * 1000)) + " ms"
        except:
            print "Couldn't start BB/BMax compiler (" + bmkCall[0] + ")"
            #if verbose:
            #    print "Call: " + bmkCall
            printTraceback()
        
        # Delete pre files
        if self.precompiler:
            self.cleanUp()
        
    def precompileModule(self, file):
        print "Precompiling module: " + file
        print "Not implemented yet"
        return
        self.precompile(file)
    
    def precompileAllModules(self, dir):
        print "Precompiling all modules: " + dir
        print "Not implemented yet"
        return
        
    def run(self):
        
        # Do nothing
        if len(sys.argv) <= 1:
            return
        
        # Load config
        self.loadConfig(self.dir + "/config.ini")
        
        appendMessages = (int(self.config.get("General", "LogAppendMessages")) != 0)
        self.log = open(self.dir + "/log.txt", ("w", "a")[appendMessages])
        self.log.write(str(sys.argv) + "\n")
        
        # Output to log
        if stripAll(sys.argv[0]) == "blitzcc":
            self.isBlitzcc = True
            sys.stdout = self.log
            sys.stderr = self.log
        elif stripAll(sys.argv[0]) == "bmk":
            self.isBmk = True
        
        # Print info
        print "\n" + self.title + " " + self.version + "\n"
        
        # Parse args
        makeApp = False
        makeMod = False
        compileBB = False
        
        # TODO: Better algorithm...
        #index = -1
        #for arg in sys.argv:
        #    index += 1
        #    if arg == "--compiler":
        #        compiler = sys.argv[index + 1]
        #        del sys.argv[index]
        #        del sys.argv[index]     # + 1
        #        print compiler
        
        #try:
        for arg in sys.argv:
            print "Argument: [" + arg + "]"
            
            # Module
            if makeMod:
                print self.dir #+ "/../mod/" + arg + ".mod"
                if os.path.isdir(self.dir + "/../mod/" + arg + ".mod"):
                    self.precompileAllModules(self.dir + "/../mod/" + arg + ".mod")
                elif os.path.isdir(self.dir + "/../mod/" + arg.replace(".", ".mod/") + ".mod"):
                    self.precompileModule(self.dir + "/../mod/" + arg.replace(".", ".mod/") + ".mod/" + arg[arg.find(".")+1:] + ".bmx")
            
            # Find bmx file
            if makeApp and arg.endswith(".bmx"):
                if not arg.endswith(".pre.bmx"):
                    self.precompile(arg)
                else:
                    makeApp = False                     # Start compiler with default arguments
            
            # Find bb file
            if arg.endswith(".bb") and not arg.endswith(".pre.bb"):
                #print "Precompile BB"
                compileBB = True
                self.precompile(fixPath(arg))
            
            #print "Done."
        
            # MakeApp
            if arg == "makeapp":
                makeApp = True
            elif arg == "makemods":
                makeMod = True
            elif arg == "-d":
                self.isDebugMode = True
                
        #except PreCompOff:
            #print "|--------------------|"
            #print "|Precompiler disabled|"
            #print "|--------------------|"
            ##precompDisabled = True
            ##self.startCompiler(True, True)
        
        # Start compiler with all default arguments
        if (self.precompiler is not None and not self.precompiler.precompEnabled) or (not compileBB and not makeApp and not makeMod):
            self.loadConfig(self.dir + "/config.ini", False)
            self.precompiler = BMaxPrecompiler(self, self.config)
            if self.isBlitzcc:
                self.precompiler.setMainFile(".bb")
            elif self.isBmk:
                self.precompiler.setMainFile(".bmx")
            self.startCompiler(True, True)
            #self.log.close()
        
        self.log.close()
        
    def cleanUp(self):
        
        # Delete pre files
        if self.precompiler.deletePreFiles:
            #print "Deleting precompiled files"
            for item in self.precompiler.importFiles.keys():
                removeSafe(item.replace(self.precompiler.extension, ".pre" + self.precompiler.extension))
            
            # Delete main file
            removeSafe(self.precompiler.mainFile.replace(self.precompiler.extension, ".pre" + self.precompiler.extension))
            
            # Delete profilerLog.pre.bmx
            removeSafe(fixPath(os.path.dirname(self.precompiler.mainFile)) + "/profilerLog.pre" + self.precompiler.extension)
